package com.allenkerr.util;


import java.awt.image.BufferedImage;
import java.io.Serializable;

/**
 * 颜色分布直方图（Color Histogram）
 * Created by 小柯 on 17/7/7.
 */
public class ColorHistogram {

    public static class HistogramData implements Serializable {
        private int total;                          //总的像素个数
        private int[] oneDimensionalHistogram;      //一维数组构成的直方图
        private int[][] twoDimensionalHistogram;    //二维数组构成的直方图

        public HistogramData(int total, int[] oneDimensionalHistogram) {
            this.total = total;
            this.oneDimensionalHistogram = oneDimensionalHistogram;
        }

        public HistogramData(int total, int[][] twoDimensionalHistogram) {
            this.total = total;
            this.twoDimensionalHistogram = twoDimensionalHistogram;
        }

        public HistogramData() {
        }

        public int getTotal() {
            return total;
        }

        public void setTotal(int total) {
            this.total = total;
        }

        public int[] getOneDimensionalHistogram() {
            return oneDimensionalHistogram;
        }

        public void setOneDimensionalHistogram(int[] oneDimensionalHistogram) {
            this.oneDimensionalHistogram = oneDimensionalHistogram;
        }

        public int[][] getTwoDimensionalHistogram() {
            return twoDimensionalHistogram;
        }

        public void setTwoDimensionalHistogram(int[][] twoDimensionalHistogram) {
            this.twoDimensionalHistogram = twoDimensionalHistogram;
        }
    }

    /**
     * 灰度直方图，只需初始化一个大小为256的直方图数组 greyHistogram，然后根据像素值
     * 完成频率分布统计，假设像素值为124，则 greyHistogram[124] += 1
     *
     * @param image
     * @return HistogramData
     */
    public static HistogramData getGreyHistogramData(BufferedImage image) {
        //初始化一个直方图数组
        int[] greyHistogram = new int[256];

        //图片的大小
        int width = image.getWidth();
        int height = image.getHeight();

        //完成灰度直方图统计
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                int rgb = image.getRGB(x, y);
                int grey = ImageUtils.rgbToGrey(rgb);
                greyHistogram[grey]++;
            }
        }
        return new HistogramData(width * height, greyHistogram);
    }

    /**
     * 彩色图像的颜色直方图
     * 方式一、对像素的每个通道都进行划分，每个通道的最大像素值为255，可以做 8、16或者64等分，等分越小，像素值取的范围越大，越精确，但图像维数就越大，时间复杂度也越大。
     * 以16等分为例，这样每个通道的范围就是0~15，该三通道得到图像维数就是 16 的三次方 4096维(从 [0,0,0] 一直到 [15,15,15] )。
     * 代码中使用某个像素点的 RGB 值得到对应的下标：r + g<<4 + b<<8，也即 r + g*16 + b*16*16。
     * 比如一个像素的 RGB 值为 [4,1,20]，那么就会有 rgbHistogram[4 + 1<<4 + 20<<8]++。
     *
     * @param image
     * @return HistogramData
     */
    public static HistogramData getOneRgbHistogramData(BufferedImage image) {
        //定义分母，也即做几等分
        final int DENOMINATOR = 16;
        //移位操作
        final int NUMBER_SHIFT = 4;

        //初始化一个直方图数组
        int[] rgbHistogram = new int[(int) Math.pow(DENOMINATOR, 3)];

        //图片大小
        int width = image.getWidth();
        int height = image.getHeight();

        //完成彩色直方图
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                int rgb = image.getRGB(x, y);
                int red = (rgb >> 16) & 0xFF;
                int green = (rgb >> 8) & 0xFF;
                int blue = (rgb) & 0xFF;
                //等分
                red = (red >> NUMBER_SHIFT) & 0xFF;
                green = (green >> NUMBER_SHIFT) & 0xFF;
                blue = (blue >> NUMBER_SHIFT) & 0xFF;
                //求下标
                int index = red + (green << NUMBER_SHIFT) + (blue << NUMBER_SHIFT << NUMBER_SHIFT);
                //统计
                rgbHistogram[index]++;
            }
        }
        return new HistogramData(width * height, rgbHistogram);
    }

    /**
     * 彩色图像的颜色直方图
     * 方式二、单独计算每个通道像素值的个数，比如一个像素点的 RGB 值为[4,1,20]，那么就有 redHistogram[4]++、greenHistogram[1]++、blueHistogram[20]++，
     * 这样就得到 3 个 256 维的一维向量，然后做叠加操作。
     *
     * @param image
     * @return HistogramData
     */
    public static HistogramData getThreeRgbHistogramData(BufferedImage image) {
        //初始化一个三维直方图数组
        int[][] rgbHistogram = new int[3][256];

        //图片大小
        int width = image.getWidth();
        int height = image.getHeight();

        //完成彩色直方图
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                int rgb = image.getRGB(x, y);
                int red = (rgb >> 16) & 0xFF;
                int green = (rgb >> 8) & 0xFF;
                int blue = (rgb) & 0xFF;
                //统计
                rgbHistogram[0][red]++;
                rgbHistogram[1][green]++;
                rgbHistogram[2][blue]++;
            }
        }
        return new HistogramData(width * height, rgbHistogram);
    }

    /**
     * 巴氏距离, 需要除以图片总的像素个数。
     * 在颜色直方图的相似度比较中，巴氏距离效果最好。
     *
     * @param aHistogram
     * @param aTotal
     * @param bHistogram
     * @param bTotal
     * @return double distance
     */
    public static double getBhattacharyyaDistance(int[] aHistogram, int aTotal, int[] bHistogram, int bTotal) {
        double distance = 0.0;
        for (int i = 0; i < aHistogram.length; i++) {
            distance += Math.sqrt((aHistogram[i] * 1.0 / aTotal) * (bHistogram[i] * 1.0 / bTotal));
        }
        return distance;
    }

    /**
     * 巴氏距离, 需要除以图片总的像素个数。
     * 在颜色直方图的相似度比较中，巴氏距离效果最好。
     *
     * @param aHistogram
     * @param aTotal
     * @param bHistogram
     * @param bTotal
     * @return double distance
     */
    public static double getBhattacharyyaDistance(int[][] aHistogram, int aTotal, int[][] bHistogram, int bTotal) {
        double distance = 0.0;
        for (int i = 0; i < aHistogram.length; i++) {
            for (int j = 0; j < aHistogram[i].length; j++) {
                distance += Math.sqrt((aHistogram[i][j] * 1.0 / aTotal) * (bHistogram[i][j] * 1.0 / bTotal));
            }
        }
        //因有 RGB 三个值，需要除以 3
        return distance / 3;
    }

    /**
     * 巴氏距离, 需要除以图片总的像素个数。
     * 在颜色直方图的相似度比较中，巴氏距离效果最好。
     *
     * @param aData
     * @param bData
     * @return double distance
     */
    public static double getBhattacharyyaDistance(HistogramData aData, HistogramData bData) {
        int[] aOneDimensionalHistogram = aData.getOneDimensionalHistogram();
        int[] bOneDimensionalHistogram = bData.getOneDimensionalHistogram();

        int[][] aTwoDimensionalHistogram = aData.getTwoDimensionalHistogram();
        int[][] bTwoDimensionalHistogram = bData.getTwoDimensionalHistogram();

        int aTotal = aData.getTotal();
        int bTotal = bData.getTotal();

        if (aOneDimensionalHistogram != null && bOneDimensionalHistogram != null)
            return getBhattacharyyaDistance(aOneDimensionalHistogram, aTotal, bOneDimensionalHistogram, bTotal);

        if (aTwoDimensionalHistogram != null && bTwoDimensionalHistogram != null)
            return getBhattacharyyaDistance(aTwoDimensionalHistogram, aTotal, bTwoDimensionalHistogram, bTotal);

        return 0.0;
    }

    /**
     * 由 灰度直方图 得到 巴氏距离
     *
     * @param aImagePath
     * @param bImagePath
     * @return double distance
     */
    public static double getBDistanceFromGreyHistogram(String aImagePath, String bImagePath) {
        //读取图片
        BufferedImage aImage = ImageUtils.getBufferedImage(aImagePath);
        BufferedImage bImage = ImageUtils.getBufferedImage(bImagePath);

        return getBDistanceFromGreyHistogram(aImage, bImage);
    }

    /**
     * 由 灰度直方图 得到 巴氏距离
     *
     * @param aImage
     * @param bImage
     * @return double distance
     */
    public static double getBDistanceFromGreyHistogram(BufferedImage aImage, BufferedImage bImage) {
        //判断是否为空
        if (aImage == null || bImage == null) {
            return 0.0;
        }

        //获取灰度直方图和像素总数
        HistogramData aData = getGreyHistogramData(aImage);
        HistogramData bData = getGreyHistogramData(bImage);

        //获取巴氏距离
        double distance = getBhattacharyyaDistance(aData, bData);

        return distance;
    }

    /**
     * 由 一维彩色直方图 得到 巴氏距离
     *
     * @param aImagePath
     * @param bImagePath
     * @return double distance
     */
    public static double getBDistanceFromOneRgbHistogram(String aImagePath, String bImagePath) {
        //读取图片
        BufferedImage aImage = ImageUtils.getBufferedImage(aImagePath);
        BufferedImage bImage = ImageUtils.getBufferedImage(bImagePath);

        return getBDistanceFromOneRgbHistogram(aImage, bImage);
    }

    /**
     * 由 一维彩色直方图 得到 巴氏距离
     *
     * @param aImage
     * @param bImage
     * @return double distance
     */
    public static double getBDistanceFromOneRgbHistogram(BufferedImage aImage, BufferedImage bImage) {
        //判断是否为空
        if (aImage == null || bImage == null) {
            return 0.0;
        }

        //获取一维彩色直方图和像素总数
        HistogramData aData = getOneRgbHistogramData(aImage);
        HistogramData bData = getOneRgbHistogramData(bImage);

        //获取巴氏距离
        double distance = getBhattacharyyaDistance(aData, bData);

        return distance;
    }

    /**
     * 由 三维彩色直方图 得到 巴氏距离
     *
     * @param aImagePath
     * @param bImagePath
     * @return double distance
     */
    public static double getBDistanceFromThreeRgbHistogram(String aImagePath, String bImagePath) {
        //读取图片
        BufferedImage aImage = ImageUtils.getBufferedImage(aImagePath);
        BufferedImage bImage = ImageUtils.getBufferedImage(bImagePath);

        return getBDistanceFromOneRgbHistogram(aImage, bImage);
    }

    /**
     * 由 三维彩色直方图 得到 巴氏距离
     *
     * @param aImage
     * @param bImage
     * @return double distance
     */
    public static double getBDistanceFromThreeRgbHistogram(BufferedImage aImage, BufferedImage bImage) {
        //判断是否为空
        if (aImage == null || bImage == null) {
            return 0.0;
        }

        //获取三维彩色直方图和像素总数
        HistogramData aData = getThreeRgbHistogramData(aImage);
        HistogramData bData = getThreeRgbHistogramData(bImage);

        //获取巴氏距离
        double distance = getBhattacharyyaDistance(aData, bData);

        return distance;
    }

}
